package cc.shacocloud.mirage.utils.annotation;

import cc.shacocloud.mirage.utils.Utils;
import cc.shacocloud.mirage.utils.charSequence.StrUtil;
import org.jetbrains.annotations.NotNull;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.*;

/**
 * 组合注解
 * <p>
 * 核心实现使用了递归获取指定元素上的注解以及注解的注解，以实现复合注解的获取。
 **/
public class CombinationAnnotationElement implements AnnotatedElementMetadata {
    
    /**
     * 注解元素
     */
    private final AnnotatedElement element;
    
    /**
     * 注解类型与注解对象对应表
     */
    private final Map<Class<? extends Annotation>, Annotation> annotationMap = new HashMap<>();
    
    /**
     * 注解类型与注解属性值的对应表
     */
    private final Map<Class<? extends Annotation>, AnnotationAttributes> annotationAttributesMap = new HashMap<>();
    
    /**
     * 直接注解类型与注解对象对应表
     */
    private final Map<Class<? extends Annotation>, Annotation> declaredAnnotationMap = new HashMap<>();
    
    /**
     * 构造
     *
     * @param element 需要解析注解的元素：可以是 Class、Method、Field、Constructor、ReflectPermission
     */
    public CombinationAnnotationElement(AnnotatedElement element) {
        this.element = element;
        init(element);
    }
    
    /**
     * 获取指定注解的所有属性值
     */
    @Override
    public <A extends Annotation> AnnotationAttributes getAnnotationAttributes(Class<A> annotationType) {
        if (annotationAttributesMap.containsKey(annotationType)) {
            return annotationAttributesMap.get(annotationType);
        }
        return null;
    }
    
    /**
     * 获取指定注解的属性值
     */
    @Override
    public <T, A extends Annotation> Optional<T> getValue(Class<A> annotationType, String attributeName, Class<T> type) {
        return Optional.ofNullable(getAnnotationAttributes(annotationType))
                .filter(annotationAttributes -> annotationAttributes.containsKey(attributeName))
                .map(annotationAttributes -> annotationAttributes.getRequiredAttribute(attributeName, type));
    }
    
    /**
     * 获取实际注解元素类型的 {@code Class} 引用
     */
    public Class<? extends AnnotatedElement> getType() {
        return element.getClass();
    }
    
    @Override
    public boolean isAnnotationPresent(@NotNull Class<? extends Annotation> annotationClass) {
        return annotationMap.containsKey(annotationClass);
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public <T extends Annotation> T getAnnotation(@NotNull Class<T> annotationClass) {
        Annotation annotation = annotationMap.get(annotationClass);
        return (annotation == null) ? null : (T) annotation;
    }
    
    @Override
    public Annotation[] getAnnotations() {
        final Collection<Annotation> annotations = this.annotationMap.values();
        return annotations.toArray(new Annotation[0]);
    }
    
    @Override
    public Annotation[] getDeclaredAnnotations() {
        final Collection<Annotation> annotations = this.declaredAnnotationMap.values();
        return annotations.toArray(new Annotation[0]);
    }
    
    /**
     * 初始化
     *
     * @param element 元素
     */
    private void init(@NotNull AnnotatedElement element) {
        
        final Annotation[] declaredAnnotations = element.getDeclaredAnnotations();
        parseDeclared(declaredAnnotations);
        
        final Annotation[] annotations = element.getAnnotations();
        parse(annotations);
    }
    
    /**
     * 进行递归解析注解，直到全部都是元注解为止
     *
     * @param annotations Class, Method, Field等
     */
    private void parseDeclared(Annotation @NotNull [] annotations) {
        Class<? extends Annotation> annotationType;
        // 直接注解
        for (Annotation annotation : annotations) {
            annotationType = annotation.annotationType();
            
            // 跳过元注解和已经处理过的注解，防止递归调用
            if (AnnotatedElementUtils.isNotMateAnnotation(annotationType) && !declaredAnnotationMap.containsKey(annotationType)) {
                
                Annotation proxyAnnotation;
                if (annotationMap.containsKey(annotationType)) {
                    proxyAnnotation = annotationMap.get(annotationType);
                } else {
                    proxyAnnotation = CombinationAnnotationInvocationHandler.createProxy(this, annotationType);
                }
                
                declaredAnnotationMap.put(annotationType, proxyAnnotation);
                // 递归注解
                parseDeclared(annotationType.getDeclaredAnnotations());
            }
        }
    }
    
    /**
     * 进行递归解析注解，直到全部都是元注解为止
     *
     * @param annotations Class, Method, Field等
     */
    private void parse(Annotation @NotNull [] annotations) {
        Class<? extends Annotation> annotationType;
        for (Annotation annotation : annotations) {
            annotationType = annotation.annotationType();
            
            // 跳过元注解
            if (AnnotatedElementUtils.isMetaAnnotation(annotationType)) {
                continue;
            }
            
            // 填充注解属性
            paddingAnnotationAttributes(annotation);
            
            // 跳过已经处理过的注解，防止递归调用
            if (!annotationMap.containsKey(annotationType)) {
                Annotation proxyAnnotation;
                if (declaredAnnotationMap.containsKey(annotationType)) {
                    proxyAnnotation = declaredAnnotationMap.get(annotationType);
                } else {
                    proxyAnnotation = CombinationAnnotationInvocationHandler.createProxy(this, annotationType);
                }
                
                annotationMap.put(annotationType, proxyAnnotation);
                
                // 递归注解
                parse(annotationType.getAnnotations());
            }
        }
    }
    
    /**
     * 填充注解的属性，支持 {@link AliasFor}
     *
     * @param annotation 注解对象
     * @see AliasFor
     */
    private void paddingAnnotationAttributes(@NotNull Annotation annotation) {
        Class<? extends Annotation> annotationType = annotation.annotationType();
        
        AttributeMethods attributeMethods = AttributeMethods.forAnnotationType(annotationType);
        AnnotationAttributes annotationAttributes = annotationAttributesMap.computeIfAbsent(annotationType, AnnotationAttributes::new);
        
        // 填充注解的属性值
        for (AttributeMethod attributeMethod : attributeMethods) {
            String attributeMethodName = attributeMethod.getName();
            Object attributeMethodValue = attributeMethod.getValue(annotation);
            Object defaultValue = attributeMethod.getDefaultValue();
            
            // 值不存在或者值不等于默认值则填充
            if (!annotationAttributes.containsKey(attributeMethodName) || !Utils.nullSafeEquals(defaultValue, attributeMethodValue)) {
                annotationAttributes.put(attributeMethodName, attributeMethodValue);
            }
            
            // 填充别名映射属性值
            paddingAliasForAttributes(attributeMethod, attributeMethodValue);
        }
    }
    
    /**
     * 基于 {@link AliasFor} 填充别名注解映射值
     * <p>
     * 当前值不等于默认值才进行别名映射
     */
    private void paddingAliasForAttributes(@NotNull AttributeMethod attributeMethod,
                                           @NotNull Object attributeMethodValue) {
        Object defaultValue = attributeMethod.getDefaultValue();
        
        if (attributeMethod.isAnnotationPresent(AliasFor.class) && !Utils.nullSafeEquals(defaultValue, attributeMethodValue)) {
            AliasFor aliasFor = attributeMethod.getAnnotation(AliasFor.class);
            String attributeAlias = aliasFor.value();
            Class<? extends Annotation> mappingAnnotation = aliasFor.annotation();
            
            AttributeMethod mappingAttributeMethod = null;
            
            // 映射注解为 Annotation 表示为本身
            if (Annotation.class.equals(mappingAnnotation)) {
                mappingAnnotation = attributeMethod.getSourceAnnotationType();
                if (StrUtil.isNotBlank(attributeAlias)) {
                    mappingAttributeMethod = attributeMethod.getAttributeMethods().get(attributeAlias);
                }
            }
            // 映射指定注解
            else {
                AttributeMethods mappingAttributeMethods = AttributeMethods.forAnnotationType(mappingAnnotation);
                attributeAlias = StrUtil.isBlank(attributeAlias) ? attributeMethod.getName() : attributeAlias;
                mappingAttributeMethod = mappingAttributeMethods.get(attributeAlias);
            }
            
            if (Objects.nonNull(mappingAttributeMethod)) {
                // 检查别名注解规则
                checkAliasForRule(attributeMethod, mappingAttributeMethod);
                
                AnnotationAttributes mappingAnnotationAttributes = annotationAttributesMap.computeIfAbsent(mappingAnnotation, AnnotationAttributes::new);
                
                Object oldValue = mappingAnnotationAttributes.put(attributeAlias, attributeMethodValue);
                
                // 如果值相同则停止递归
                if (!Utils.nullSafeEquals(oldValue, attributeMethodValue)) {
                    paddingAliasForAttributes(mappingAttributeMethod, attributeMethodValue);
                }
                
            }
        }
        
    }
    
    /**
     * 检查别名注解规则
     *
     * <ul>
     *     <li>别名属性必须声明相同的返回类型</li>
     *     <li>别名属性必须声明默认值</li>
     * </ul>
     *
     * @param attributeMethod        注解的属性方法
     * @param mappingAttributeMethod 映射注解的属性方法
     * @see AliasFor
     */
    private void checkAliasForRule(@NotNull AttributeMethod attributeMethod, @NotNull AttributeMethod mappingAttributeMethod) {
        // 别名属性必须声明默认值
        if (attributeMethod.getDefaultValue() == null || mappingAttributeMethod.getDefaultValue() == null) {
            throw new IllegalArgumentException(String.format("注解别名方法 %s 和其映射的别名方法 %s 都应该声明默认值！",
                    Utils.methodDescription(attributeMethod.getSourceMethod()), Utils.methodDescription(mappingAttributeMethod.getSourceMethod())));
        }
        
        // 别名属性必须声明相同的返回类型
        if (!attributeMethod.getReturnType().equals(mappingAttributeMethod.getReturnType())) {
            throw new IllegalArgumentException(String.format("注解别名方法 %s 和其映射的别名方法 %s 必须声明相同的返回类型！",
                    Utils.methodDescription(attributeMethod.getSourceMethod()), Utils.methodDescription(mappingAttributeMethod.getSourceMethod())));
        }
    }
    
}
